/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    archiver.cpp  
  Description: 

*/

#include "stdafx.h"

using namespace m::alloc;

#define ARRAYINDEX_MAX 13

#define TOK_ROOT L"root"
#define TOK_RESERVED L"$__"
#define TOK_COUNTOBJECTS L"$__CountObjects"
#define TOK_VALUE L"$__Value"
#define TOK_OBJID L"$__Id"
#define TOK_HASDATA L"$__HasData"

namespace m
{
	archiver::archiver()
		: _Serializer(nullptr),
		_MaxId(0),
		_Array(nullptr),
		_Fixup(nullptr),
		_Objects(nullptr)
	{
		if (!InitializeCriticalSectionAndSpinCount(&_ArchiverLock, CRIT_WAIT))
		{
			throw bad_result_exception(__HRESULT_FROM_WIN32(GetLastError()), "Failed to initialize archiver.");
		}
	}
	
	archiver::~archiver()
	{
		clear(_Serializer);

		ArrayState* cur = _Array;
		while (cur)
		{
			ArrayState* toDelete = cur;
			cur = cur->_LastState;
			delete toDelete;
		}

		ReleaseObjects();
	}

	void archiver::archive(object * root, m::serializer * serializer)
	{
		if (root)
		{
			assign(_Serializer, serializer);

			std::exception_ptr exptr;
			EnterCriticalSection(&_ArchiverLock);
			try
			{
				_MaxId = 0;
				WriteValue(TOK_ROOT, root, true);
			}
			catch (...)
			{
				exptr = std::current_exception();
			}

			ReleaseObjects();
			LeaveCriticalSection(&_ArchiverLock);
			if (exptr)
			{
				std::rethrow_exception(exptr);
			}
		}
	}

	object* Broken()
	{
		throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_BAD_ARGUMENTS), "Root object cannot be default constructed.");
	}
	
	void archiver::unarchive(object * root, m::serializer * serializer)
	{
		if (root)
		{
			assign(_Serializer, serializer);

			std::exception_ptr exptr;
			EnterCriticalSection(&_ArchiverLock);
			try
			{
				if (!ReadValue(TOK_ROOT, &root, CtorFuncType(Broken), true))
				{
					throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_FILE_CORRUPT), "Couldn't read archive.");
				}
			}
			catch (...)
			{
				exptr = std::current_exception();
			}

			ReleaseObjects();
			LeaveCriticalSection(&_ArchiverLock);
			if (exptr)
			{
				std::rethrow_exception(exptr);
			}
		}
	}
	
	void archiver::write_array_start(const wchar_t *name)
	{
		assert(name);
		if (name)
		{
			ArrayState* state = new ArrayState();
			state->_LastState = _Array;
			_Array = state;
			_Serializer->write_object_start(name);
		}
	}

	void archiver::write_array_start()
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize array values.");
		}

		int64_t* pSize = &_Array->MaxSize;
		write_array_start(ArrayIndex(_Array->MaxSize));
		++*pSize;
	}
	
	void archiver::write_array_value(object * obj)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize object values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), obj);
		++_Array->MaxSize;
	}
	
	void archiver::write_array_value(double num)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize double values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), num);
		++_Array->MaxSize;
	}

	void archiver::write_array_value(int8_t num)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize double values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), num);
		++_Array->MaxSize;
	}

	void archiver::write_array_value(int16_t num)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize double values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), num);
		++_Array->MaxSize;
	}

	void archiver::write_array_value(int32_t num)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize int values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), num);
		++_Array->MaxSize;
	}

	void archiver::write_array_value(int64_t num)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize long values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), num);
		++_Array->MaxSize;
	}
	
	void archiver::write_array_value(const wchar_t * string)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize string values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), string);
		++_Array->MaxSize;
	}
	
	void archiver::write_array_value(bool bit)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize bool values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), bit);
		++_Array->MaxSize;
	}

	void archiver::write_array_value(const byte * data, size_t sz)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot serialize bool values.");
		}

		write_value(ArrayIndex(_Array->MaxSize), data, sz);
		++_Array->MaxSize;
	}
	
	void archiver::write_array_end()
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot write array end.");
		}

		_Serializer->write_object_end();
		ArrayState* toDelete = _Array;
		_Array = _Array->_LastState;
		delete toDelete;
	}
	
	void archiver::write_value(const wchar_t * name, object * obj)
	{
		assert(name);
		if (name)
		{
			if (wcsstr(name, TOK_RESERVED) != nullptr)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_BAD_ARGUMENTS), "Invalid name. Cannot serialize object value.");
			}

			WriteValue(name, obj, false);
		}
	}
	
	void archiver::write_value(const wchar_t * name, double num)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, num);
		}
	}

	void archiver::write_value(const wchar_t * name, int8_t num)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, num);
		}
	}

	void archiver::write_value(const wchar_t * name, int16_t num)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, num);
		}
	}

	void archiver::write_value(const wchar_t * name, int32_t num)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, num);
		}
	}

	void archiver::write_value(const wchar_t * name, int64_t num)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, num);
		}
	}
	
	void archiver::write_value(const wchar_t * name, const wchar_t * string)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, string);
		}
	}
	
	void archiver::write_value(const wchar_t * name, bool bit)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, bit);
		}
	}

	void archiver::write_value(const wchar_t * name, const byte * data, size_t sz)
	{
		assert(name);
		if (name)
		{
			_Serializer->write_value(name, data, sz);
		}
	}
	
	bool archiver::read_array_start(const wchar_t* name, size_t* sz)
	{
		assert(name);
		if (name)
		{
			ArrayState* state = new ArrayState();
			state->_LastState = _Array;
			_Array = state;
			size_t countChildren;
			if (_Serializer->read_object_start(name, &countChildren))
			{
				assert(countChildren <= SIZE_MAX);
				if (countChildren <= SIZE_MAX)
				{
					_Array->MaxSize = countChildren;
				}
				if (sz)
				{
					*sz = (size_t)_Array->MaxSize;
				}
				return true;
			}
		}
		return false;
	}

	bool archiver::read_array_start(size_t index, size_t * sz)
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read int value.");
		}

		if ((int64_t)index >= _Array->MaxSize)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for int value.");
		}

		return read_array_start(ArrayIndex(index), sz);
	}

	void archiver::read_array_value(size_t index, int8_t * num)
	{
		assert(num);
		if (num)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read int value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for int value.");
			}

			read_value(ArrayIndex(index), num);
		}
	}

	void archiver::read_array_value(size_t index, int16_t * num)
	{
		assert(num);
		if (num)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read int value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for int value.");
			}

			read_value(ArrayIndex(index), num);
		}
	}

	void archiver::read_array_value(size_t index, int32_t * num)
	{
		assert(num);
		if (num)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read int value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for int value.");
			}

			read_value(ArrayIndex(index), num);
		}
	}

	void archiver::read_array_value(size_t index, int64_t * num)
	{
		assert(num);
		if (num)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read long value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for long value.");
			}

			read_value(ArrayIndex(index), num);
		}
	}
	
	void archiver::read_array_value(size_t index, double * num)
	{
		assert(num);
		if (num)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read number value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for number value.");
			}

			read_value(ArrayIndex(index), num);
		}
	}
	
	void archiver::read_array_value(size_t index, wchar_t ** string)
	{
		assert(string);
		if (string)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read string value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for string value.");
			}

			read_value(ArrayIndex(index), string);
		}
	}
	
	void archiver::read_array_value(size_t index, bool * bit)
	{
		assert(bit);
		if (bit)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read bool value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for bool value.");
			}

			read_value(ArrayIndex(index), bit);
		}
	}

	void archiver::read_array_value(size_t index, byte ** data, size_t * sz)
	{
		assert(data);
		if (data)
		{
			if (!_Array)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read bool value.");
			}

			if ((int64_t)index >= _Array->MaxSize)
			{
				throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_NOT_FOUND), "Array index out of range for bool value.");
			}

			read_value(ArrayIndex(index), data, sz);
		}
	}
	
	void archiver::read_array_end()
	{
		if (!_Array)
		{
			throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_INVALID_OPERATION), "Not in array. Cannot read array end.");
		}

		_Serializer->read_object_end();
		ArrayState* toDelete = _Array;
		_Array = _Array->_LastState;
		delete toDelete;
	}
	
	void archiver::read_value(const wchar_t * name, double * num)
	{
		assert(name && num);
		if (name && num)
		{
			_Serializer->read_value(name, num);
		}
	}

	void archiver::read_value(const wchar_t * name, int8_t * num)
	{
		assert(name && num);
		if (name && num)
		{
			_Serializer->read_value(name, num);
		}
	}

	void archiver::read_value(const wchar_t * name, int16_t * num)
	{
		assert(name && num);
		if (name && num)
		{
			_Serializer->read_value(name, num);
		}
	}

	void archiver::read_value(const wchar_t * name, int32_t * num)
	{
		assert(name && num);
		if (name && num)
		{
			_Serializer->read_value(name, num);
		}
	}

	void archiver::read_value(const wchar_t * name, int64_t * num)
	{
		assert(name && num);
		if (name && num)
		{
			_Serializer->read_value(name, num);
		}
	}
	
	void archiver::read_value(const wchar_t * name, wchar_t ** string)
	{
		assert(name && string);
		if (name && string)
		{
			_Serializer->read_value(name, string);
		}
	}
	
	void archiver::read_value(const wchar_t * name, bool * bit)
	{
		assert(name && bit);
		if (name && bit)
		{
			_Serializer->read_value(name, bit);
		}
	}

	void archiver::read_value(const wchar_t * name, byte ** data, size_t * sz)
	{
		assert(name && data);
		if (name && data)
		{
			_Serializer->read_value(name, data, sz);
		}
	}

	const wchar_t *archiver::ArrayIndex(int64_t index)
	{
		StringCchPrintf(_Buf, ARRAYSIZE(_Buf), L"%lld", index);
		return _Buf;
	}

	void archiver::TrackObject(object* obj)
	{
		assert(obj);
		if (obj)
		{
			if (obj->_Archiver != this)
			{
				PushObject(obj);
				// Lock the archiver object as us until we release all tracked objects.
				while (InterlockedCompareExchangePointer((volatile PVOID*)&obj->_Archiver, this, nullptr) != this);
			}
		}
	}

	void archiver::ReleaseObjects()
	{
		if (_Fixup)
		{
			for (size_t i = 0; i < (size_t)_MaxId; ++i)
			{
				clear(_Fixup[i]);
			}
			_Fixup = nullptr;
		}

		while (_Objects)
		{
			_Objects->Object->_Archiver = nullptr;
			_Objects->Object->_Id = ID_NOTARCHIVED;
			PopObject();
		}
	}

	void archiver::WriteValue(const wchar_t* name, object* obj, bool root)
	{
		if (obj)
		{
			// Now serialize the object to this location.
			TrackObject(obj);
			_Serializer->write_object_start(name);
			bool hasData = false;
			if (obj->_Id < 0)
			{
				hasData = true;
				obj->_Id = _MaxId;
				++_MaxId;
				obj->write(this);
			}
			_Serializer->write_value(TOK_HASDATA, hasData);
			_Serializer->write_value(TOK_OBJID, (int64_t)obj->_Id);
			if (root)
			{
				_Serializer->write_value(TOK_COUNTOBJECTS, (int64_t)_MaxId);
			}
			_Serializer->write_object_end();
		}
	}

	bool archiver::ReadValue(const wchar_t* name, object** object, CtorFuncType& ctorFunc, bool root)
	{
		assert(object);
		if (object)
		{
			// We should expect to read the object ref here.
			if (_Serializer->read_object_start(name, nullptr))
			{
				if (root)
				{
					_Serializer->read_value(TOK_COUNTOBJECTS, (int64_t*)&_MaxId);
					if (_MaxId < 0)
					{
						throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_FILE_CORRUPT), "Invalid size parameter for archive.");
					}
					_Fixup = new m::object*[_MaxId];
					for (size_t i = 0; i < (size_t)_MaxId; ++i)
					{
						_Fixup[i] = nullptr;
					}
				}
				bool hasData;
				_Serializer->read_value(TOK_HASDATA, &hasData);
				int64_t ObjId;
				_Serializer->read_value(TOK_OBJID, &ObjId);
				if (ObjId < 0 || ObjId >= _MaxId)
				{
					throw bad_result_exception(MONORET(FACILITY_ARCHIVE, ERROR_FILE_CORRUPT), "Invalid object id in archive.");
				}

				if (!_Fixup[ObjId])
				{
					if (!*object)
					{
						assign_new(*object, ctorFunc());
					}
					assign(_Fixup[ObjId], *object);
				}
				else
				{
					assign(*object, _Fixup[ObjId]);
				}
				TrackObject(*object);

				if (hasData)
				{
					_Fixup[ObjId]->read(this);
				}

				_Serializer->read_object_end();
				return true;
			}
		}
		return false;
	}
	
	archiver::ArrayState::ArrayState()
		: MaxSize(0),
		_LastState(nullptr)
	{
	}

	archiver::ArrayState::~ArrayState()
	{
	}
	
	void * archiver::ArrayState::operator new(size_t sz)
	{
		return ::operator new(sz, pool);
	}

	void archiver::ArrayState::operator delete(void * ptr)
	{
		return ::operator delete(ptr, pool);
	}

	archiver::ObjectState::ObjectState()
		: Object(nullptr),
		Last(nullptr)
	{
	}

	archiver::ObjectState::~ObjectState()
	{
		m::clear(Object);
	}

	void * archiver::ObjectState::operator new(size_t sz)
	{
		return ::operator new(sz, m::alloc::pool);
	}

		void archiver::ObjectState::operator delete(void * ptr)
	{
		return ::operator delete(ptr, m::alloc::pool);
	}

	void archiver::PushObject(object* object)
	{
		assert(object);
		if (object)
		{
			ObjectState* newState = new ObjectState();
			assign(newState->Object, object);
			newState->Last = _Objects;
			_Objects = newState;
		}
	}

	void archiver::PopObject()
	{
		if (_Objects)
		{
			ObjectState* toDelete = _Objects;
			_Objects = _Objects->Last;
			delete toDelete;
		}
	}
}